/**
 * File: FastqSplitUtil.java
 * Created by: mhaimel
 * Created on: 8 Apr 2009
 * CVS:  $Id: FastqSplitUtil.java,v 1.2 2009/06/19 13:02:49 mhaimel Exp $
 */
package uk.ac.ebi.curtain.utils.file.impl;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.data.IdentifierProcessor;
import uk.ac.ebi.curtain.utils.file.SplitUtil;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.model.Strand;

/**
 * @author mhaimel
 *
 */
public class FastqSplitUtil implements SplitUtil{
	private Log log = null;
	
	private Log getLog() {
		if(null == log){
			log =  LogFactory.getLog(this.getClass());
		}
		return log;
	}

	@Override
	public void split(IdentifierProcessor fromPar, IdentifierProcessor toPar, BufferedReader in, SplitFileProcessor procPar) throws IOException{
		final IdentifierProcessor from = fromPar;
		final IdentifierProcessor to = toPar;
		final SplitFileProcessor proc = procPar;
		split(in, new SplitFileProcessor(){
			@Override
			public boolean processLine(String line, long lineNumber,long entryNumber,boolean startOfNewEntry) {
				if(startOfNewEntry){
					line = convertLine(from, to, line,lineNumber);
				}
				return proc.processLine(line, lineNumber,entryNumber, startOfNewEntry);
			}});
	}
	
	private void split(BufferedReader in, SplitFileProcessor proc) throws IOException{
		boolean doRead = true;
		boolean soe = true;
		long lineCnt = 0;
		long entryCnt = -1;
		String line = null;
		while(doRead && (line = in.readLine()) != null){
			soe = lineCnt%4 == 0;
			if(soe){
				++entryCnt;
			} 
			doRead = proc.processLine(
					line,
					lineCnt,
					entryCnt,
					soe // start of Entry - new entry starts now
					);	
			++lineCnt;
		}
	}	
	
	@Override
	public void split(IdentifierProcessor fromPar, IdentifierProcessor toPar, File source, File ... targetArr) throws CurtainUncheckedException{
		final IdentifierProcessor from = fromPar;
		final IdentifierProcessor to = toPar;
		getLog().debug("Split " + source + " into " + targetArr.length + " files: " +StringUtils.join(targetArr, ','));
		
		BufferedReader in = null;
		final SplitMultipleFileProcessor fileProc = new SplitMultipleFileProcessor(targetArr);
		try{
			in = new FileIO(source).getBufferedReader();
			fileProc.openAll();
			split(in,new SplitFileProcessor(){
				@Override
				public boolean processLine(String line, long lineNumber,long entryNumber,boolean startOfNewEntry) {
					if(startOfNewEntry){
						line = convertLine(from, to, line,lineNumber);
					}
					return fileProc.processLine(line, lineNumber,entryNumber, startOfNewEntry);
				}});
		} catch (Exception e) {
			String msg = "Problems reading from " 
				+ source 
				+ " and writing to "
				+ StringUtils.join(targetArr, ',');
			throw new CurtainUncheckedException(msg,e);
		}finally{
			FileIO.closeQuietly(in);
			FileIO.closeQuietly(fileProc);
		}
	}
	
	private String convertLine(IdentifierProcessor from, IdentifierProcessor to, String line, long lineNumber){
		if(null != from && null != to && StringUtils.isNotBlank(line)){
			String id = line;
			String rest = StringUtils.EMPTY;
			int spaceIdx = id.indexOf(' ');
			if(spaceIdx > 0){
				id = line.substring(0, spaceIdx);
				rest = line.substring(spaceIdx+1);
			}
			Strand strand = from.getStrand(id);
			if(strand == null){
				throw new IllegalStateException("No Strand information found in id: line number: "+ lineNumber+"; line: " + line);
			}
			String cleanId = from.removeStrandInformation(id, false);
			id = to.addStrandInformation(
					cleanId, strand);
			
			if(spaceIdx > 0){
				id = id+' '+rest;
			}
			if(id.charAt(0) != '@'){
				id = "@"+id;
			}
			line = id;
		}
		return line;
	}
	
	@Override
	public void split(File info, File ... fileArr) throws CurtainUncheckedException{
		split(null,null,info, fileArr);
	}
	
	public static class SplitMultipleFileProcessor implements SplitFileProcessor, Closeable{
		
		private final File[] targetArr;
		private final PrintWriter[] outArr;

		public SplitMultipleFileProcessor(File ... targetArr) {
			this.targetArr = targetArr;
			this.outArr = new PrintWriter[targetArr.length];
		}
		
		public void openAll() throws IOException{
			for(int i = 0; i < targetArr.length; ++i){
				File f = targetArr[i];
				outArr[i] = new FileIO(f).getPrintWriter();
			}
		}

		@Override
		public boolean processLine(String line, long lineNumber, long entryNumber,boolean startOfNewEntry) {
			PrintWriter pw = getOutput(getFileNumber(entryNumber));
			pw.println(line);
			return true;
		}

		protected int getFileNumber(long entryNumber) {
			return (int) entryNumber%outArr.length;
		}

		protected PrintWriter getOutput(int fileNumber) {
			return outArr[fileNumber];
		}

		@Override
		public void close() throws IOException {
			for(int i = 0; i < outArr.length; ++i){
				IOUtils.closeQuietly(outArr[i]);
			}
		}	
		
	}

	
}
